package org.jvnc.vnc;

/**
* <br><br><center><table border="1" width="80%"><hr>
* <p>
* Copyright (C) 2006-2007 by Deon Wu
* <p>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.0
* of the License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU Lesser General Public License</a>
* for more details.
* <p>
* You should have received a copy of the <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">
* GNU Lesser General Public License</a> along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* <hr></table></center>
* **/

import gnu.rfb.PixelFormat;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.Socket;
import java.util.Collection;
import java.util.Vector;

import org.jvnc.logging.VLog;
import org.jvnc.rfb.RFBSocketClient;
import org.jvnc.rfb.event.*;
import org.jvnc.rfb.listener.*;
import org.jvnc.rfb.proto.*;
import org.jvnc.rfb.proto.client.*;
import org.jvnc.rfb.proto.server.*;

public class VncClient {
	private static final VLog log = VLog.getLog(VncClient.class);
	
	private Collection<VncScreen> screens = new Vector<VncScreen>();
	private RFBSocketClient rfbClient;
	private ServerStatus serverStatus = null;
	
	//	private Socket socket = null;
	private VncScreen mainScreen = null;
	private VncScreen controlScreen = null;
	
	//VNC客户端的输出缓存，保存从服务器端传输到客户端的原始图像。
	private Image frameBuffer;
	private VncInputListener inputListener = null;
	//
	//private boolean isInteractive = false;
	
	public VncClient(){
		mainScreen = new VncScreen();
		inputListener = new VncInputListener(this);
		inputListener.setIsActive(false);
		registerScreen(mainScreen,true);
	}
	
	/**
	 * Create a VNC client by host. 
	 * @param host
	 * @param port
	 */
	public VncClient(String host, int port){
		this();
		rfbClient = new RFBSocketClient(host,port,"RFB 003.008\n");
		initVNCClient();		
	}
	
	public void connect(String host, int port, String password){
		if(rfbClient != null){
			try {
				rfbClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		rfbClient = new RFBSocketClient(host,port,"RFB 003.008\n");
		initVNCClient();

		connect(password);
	}
	
	public void connect(String password){
		if(rfbClient != null){
			rfbClient.setPassowrd(password);
			rfbClient.start();
			
			//连接后把控制权
			inputListener.handOverControl(controlScreen);
		}
	}
	
	public void sendCtrlAltDelete(){
		if(inputListener != null){
			inputListener.sendCtrlAltDelete();
		}
	}
	
	/**
	 * 注册一个显示面版。 一个VNC可以有多个显示面板，但只能有一个是主控的可以操作的。
	 * @param scr
	 * @param isControl 是否取得控制权。
	 */
	public void registerScreen(VncScreen scr, boolean isControl){
		screens.add(scr);
		scr.setBufferScreen(frameBuffer);
		
		if(isControl){
			controlScreen = scr;
			inputListener.handOverControl(scr);
		}
		scr.repaint();		
	}
	
	public VncScreen getVncScreen(){
		return mainScreen;
	}

	public int getPreferredEncoding(){
		return 0;
	}
	public void setPreferredEncoding(int encoding ){
		
	}
	
	//更新所有注册到本客户端的Screen.
	protected void updateScreen(){
		for(VncScreen s : screens){
			s.setBufferScreen(frameBuffer);
			s.repaint();
		}
	}
	protected void updateScreen(int x, int y, int width, int height){
		for(VncScreen s : screens){
			s.setBufferScreen(frameBuffer);
			s.repaint(x,y,width,height);
		}
	}


	public void bell(Bell b) {
		Toolkit.getDefaultToolkit().beep();	
	}



	public void updateColorMap(ColorMapEntries e) {
		log.info("Not support set ColorMap in this verison!");
	}

	/**
	 * 更新客户端帧缓存，由RFB中的事件触发。 
	 * @param frame
	 */
	public void updateFrame(FrameBufferUpdate frames) {
		
		Graphics memGraphics = frameBuffer.getGraphics();
		for(Rectangle rect : frames.getRects()){
			memGraphics.setClip(rect.getPositionX(), rect.getPositionY(),
								rect.getWidth(), rect.getHeight());
			Image updatedFrame = rect.getEncoding().deCode(this.frameBuffer); 
			if (null != updatedFrame) {
				memGraphics.drawImage(updatedFrame, rect.getPositionX(), 
										rect.getPositionY(), null);
			}
			updateScreen(rect.getPositionX(),rect.getPositionY(),
						 rect.getWidth(),rect.getHeight());
		}
		memGraphics.setClip(0, 0,
				frameBuffer.getWidth(null),
				frameBuffer.getHeight(null));
		//updateScreen();
	}



	public void cutText(CutText text) {
		log.info("Not support set CutText in this verison!");		
	}
	
	/**
	 * 设置客户端的像素格式。
	 */
	public void setPixelFormat(PixelFormat format){
		try {
			rfbClient.sendDataGram(new SetPixelFormat(format));
		} catch (IOException e) {
			log.error(e.toString(),e);
		}				
	}
	
	/**
	 * 设置客户端的编码格式。
	 */
	public void setEncoding(int[] encoding){
		try {
			rfbClient.sendDataGram(new SetEncodings(encoding));
		} catch (IOException e) {
			log.error(e.toString(),e);
		}			
	}

	public void requestFrameUpdate(int x,int y, int width,int height, boolean isIncrement) {
		try {
			rfbClient.sendDataGram(new FramebufferUpdateRequest(x,y,width,height,isIncrement));
		} catch (IOException e) {
			log.error(e.toString(),e);
		}
	}

	public void refreshFrame() {
		refreshFrame(true);
	}	
	public void refreshFrame(boolean isIncrement) {
		if(serverStatus != null){
			requestFrameUpdate(0,
					0,
					serverStatus.getFrameWidth(),
					serverStatus.getFrameHeight(),isIncrement);
		}
	}
	
	public RFBSocketClient getRFBSokcet(){
		return this.rfbClient;
	}
	
	public boolean isConnected(){
		return rfbClient != null;
	}
	
	public void close(){
		try {
			if(rfbClient != null){
				rfbClient.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			rfbClient = null;			
		}
	}

	
	private void initVNCClient(){
		
		//当服务端状态更新时，保存状态。
		rfbClient.regListener(new ServerStatusListener(){
			public void handle(ServerStatusEvent e) {
				System.out.println("initVNCClient");
				serverStatus = e.getServerStatus();
				initClientFrameBuffer();
				//设置客户端支持的编码方式。
				setEncoding(new int[]{0,1,2});
				requestFrameUpdate(0,
						0,
						serverStatus.getFrameWidth(),
						serverStatus.getFrameHeight(),false);
				//输入控制开始响应。
				inputListener.setIsActive(true);
			}
		});
		
		/*
		 	//RfbClientListener
			注册服务器端到客户端的消息处理接口。将RFB协议内容，委派给VNC客户端处理。
			实现了RFB本身只是封协议，VNC才是协议的上层应用。
			
			没有在本类实现RfbClientListener然后注册到RFBClient，因为RFBClinet的事件触发时使用反射
			机制遍历了监听器。方法太多会影响效。但又需要保持对象的封状特性，所以使用了inner类进行转发。
		*/
		
		rfbClient.regListener(new RfbClientListener(){
			public void handle(BellEvent e) {
				bell(e.getBell());
			}
			public void handle(ColorMapEvent e) {
				updateColorMap(e.getColorMapEntries());
			}
			public void handle(BufferUpdateEvent e) {
				updateFrame(e.getFrameBufferUpdate());
			}
			public void handle(CutTextEvent e) {
				cutText(e.getCutText());
			}
		});
	}
	
	private synchronized void initClientFrameBuffer(){
		if(frameBuffer == null ||
				frameBuffer.getWidth(null) != serverStatus.getFrameWidth() &&
				frameBuffer.getHeight(null) != serverStatus.getFrameHeight()
		){
			frameBuffer = new BufferedImage((int)serverStatus.getFrameWidth(),
											(int)serverStatus.getFrameHeight(),
											 	 BufferedImage.TYPE_3BYTE_BGR);

		}
	}
}

